home *** CD-ROM | disk | FTP | other *** search
- unit SocketManager;
- {*******************************************************************************
- TCP/IP Chat Demo
- Written by David Clegg, davidclegg@optusnet.com.au.
- *******************************************************************************}
-
- interface
-
- uses
- System.Net.Sockets, System.Threading, System.Collections;
-
- type
- TByteArray = array[0..255] of Byte;
-
- TDataReceivedEvent = procedure(Sender: TObject; const pData: string) of object;
- TErrorEvent = procedure(Sender: TObject; E: Exception) of object;
- TSocketEvent = procedure(Sender: TObject; pSocket: Socket);
-
- TStateObject = class
- strict private
- FWorkSocket: Socket;
- FBufferSize: integer;
- //FBuffer: TByteArray;
- public
- //The buffer must be a public field otherwise it will not be populated when
- //being passed to methods such as Socket.BeginReceive
- Buffer: TByteArray;
- //property Buffer: TByteArray read FBuffer write FBuffer;
- property WorkSocket: Socket read FWorkSocket write FWorkSocket;
- property BufferSize: Integer read FBufferSize write FBufferSize;
- constructor Create(pWorkSocket: Socket);
- end;
-
- TSocketManager = class
- private
- FClientSocket: Socket;
- FServerSocket: Socket;
- FOnClientConnected: TSocketEvent;
- FOnClientDisconnected: TSocketEvent;
- FStopEvent: ManualResetEvent;
- FConnectEvent: ManualResetEvent;
- FTransmitLock: ReaderWriterLock;
- FReceiveLock: ReaderWriterLock;
- FTransmitQueue: Queue;
- FOnDataReceived: TDataReceivedEvent;
- FOnSendError: TErrorEvent;
- FOnReceiveError: TErrorEvent;
- FOnListenError: TErrorEvent;
- FOnConnectError: TErrorEvent;
- FListening: boolean;
- FConnected: boolean;
- procedure SendThreadEntryPoint(pState: TObject);
- procedure ReceiveThreadEntryPoint(pState: TObject);
- procedure ListenThreadEntryPoint(pState: TObject);
- procedure SendCallback(pAsyncResult: IAsyncResult);
- procedure ReceiveCallback(pAsyncResult: IAsyncResult);
- procedure AcceptCallback(pAsyncResult: IAsyncResult);
- procedure InitResetEvents;
- public
- property OnDataReceived: TDataReceivedEvent read FOnDataReceived write FOnDataReceived;
- property OnSendError: TErrorEvent read FOnSendError write FOnSendError;
- property OnReceiveError: TErrorEvent read FOnReceiveError write FOnReceiveError;
- property OnListenError: TErrorEvent read FOnListenError write FOnListenError;
- property OnConnectError: TErrorEvent read FOnConnectError write FOnConnectError;
- property OnClientConnected: TSocketEvent read FOnClientConnected write FOnClientConnected;
- property OnClientDisconnected: TSocketEvent read FOnClientDisconnected write FOnClientDisconnected;
- property ClientSocket: Socket read FClientSocket;
- property ServerSocket: Socket read FServerSocket;
- property Connected: boolean read FConnected write FConnected;
- property Listening: boolean read FListening write FListening;
- procedure Listen;
- procedure StopListening;
- procedure Connect(const pAddress: string; const pPort: integer);
- procedure Disconnect;
- procedure SendText(const pText: string);
- constructor Create;
- end;
-
- implementation
-
- uses
- System.Net, System.Text, SysUtils, System.Windows.Forms;
-
- { TStateObject }
- constructor TStateObject.Create(pWorkSocket: Socket);
- begin
- inherited Create;
- FBufferSize := 256;
- FWorkSocket := pWorkSocket;
- end;
-
- { TSocketManager }
- constructor TSocketManager.Create;
- begin
- inherited Create;
- InitResetEvents;
- end;
-
- /// <summary>
- /// Create the objects required to control the threads.
- /// </summary>
- procedure TSocketManager.InitResetEvents;
- begin
- //Create Reset Event instances
- FStopEvent := ManualResetEvent.Create(False);
- FConnectEvent := ManualResetEvent.Create(False);
-
- //Create ReaderWriterLock instances
- FTransmitLock := ReaderWriterLock.Create;
- FReceiveLock := ReaderWriterLock.Create;
-
- //Create Transmit and Receive Queue instances
- FTransmitQueue := Queue.Create;
- end;
-
- /// <summary>
- /// Listen for client connections on the server socket.
- /// </summary>
- procedure TSocketManager.Listen;
- var
- lEndPoint: IPEndPoint;
- lHostEntry: IPHostEntry;
- begin
- try
- FStopEvent.Reset;
- lHostEntry := Dns.Resolve(Dns.GetHostName);
- if Length(lHostEntry.AddressList) <> 0 then
- begin
- lEndPoint := IPEndPoint.Create(lHostEntry.AddressList[0], 1024);
- FServerSocket := Socket.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.TCP);
- FServerSocket.Bind(lEndPoint);
-
- //Pool the Listen thread entry point
- ThreadPool.QueueUserWorkItem(ListenThreadEntryPoint);
- FListening := True;
- end;
- except
- on E: Exception do
- if Assigned(FOnListenError) then
- FOnListenError(Self, E);
- end;
- end;
-
- /// <summary>
- /// Signal to the server worker threads that they should stop.
- /// </summary>
- procedure TSocketManager.StopListening;
- begin
- FStopEvent.&Set;
- FListening := False;
- end;
-
- /// <summary>
- /// Connect to the TCP/IP server.
- /// </summary>
- /// <param name="pAddress">Address of TCP/IP Server to connect to.</param>
- /// <param name="pPort">Port of TCP/IP Server to connect to.</param>
- procedure TSocketManager.Connect(const pAddress: string; const pPort: integer);
- var
- lEndPoint: IPEndPoint;
- lHostEntry: IPHostEntry;
- begin
- try
- FStopEvent.Reset;
- lHostEntry := Dns.Resolve(pAddress);
- if Length(lHostEntry.AddressList) <> 0 then
- begin
- lEndPoint := IPEndPoint.Create(lHostEntry.AddressList[0], pPort);
- FClientSocket := Socket.Create(lEndPoint.AddressFamily, SocketType.Stream, ProtocolType.TCP);
- //No need to connect asynchronously
- FClientSocket.Connect(lEndPoint);
-
- //Pool the send and seceive thread entry points
- ThreadPool.QueueUserWorkItem(ReceiveThreadEntryPoint);
- ThreadPool.QueueUserWorkItem(SendThreadEntryPoint);
- FConnected := True;
- end;
- except on e: Exception do
- FOnConnectError(Self, e);
- end;
- end;
-
- /// <summary>
- /// Send text through the socket connection.
- /// </summary>
- /// <param name="pText">The text to send.</param>
- procedure TSocketManager.SendText(const pText: string);
- begin
- if Assigned(FClientSocket) and FClientSocket.Connected then
- begin
- FTransmitLock.AcquireWriterLock(-1);
- try
- FTransmitQueue.Enqueue(pText);
- finally
- FTransmitLock.ReleaseWriterLock;
- end;
- end;
- end;
-
- /// <summary>
- /// Disconnect the client socket, and signal the worker threads to stop.
- /// </summary>
- procedure TSocketManager.Disconnect;
- begin
- //signal the threads to end
- FStopEvent.&Set;
- if Assigned(FClientSocket) then
- begin
- //Disable sending and receiving on the socket
- FClientSocket.Shutdown(SocketShutdown.Both);
- //Close the socket
- FClientSocket.Close;
- FreeAndNil(FClientSocket);
- end;
- FConnected := False;
- end;
-
- //***** TCP/IP Asynchronous Callback Methods *****
-
- /// <summary>
- /// Callback method called by the asynchronous BeginSend method.
- /// </summary>
- procedure TSocketManager.SendCallback(pAsyncResult: IAsyncResult);
- var
- lSocket: Socket;
- begin
- lSocket := Socket(pAsyncResult.AsyncState);
- try
- lSocket.EndSend(pAsyncResult);
- except
- on e: Exception do
- if Assigned(FOnSendError) then
- FOnSendError(lSocket, e);
- end;
- end;
-
- /// <summary>
- /// Callback method called by the asynchronous BeginReceive method.
- /// </summary>
- procedure TSocketManager.ReceiveCallback(pAsyncResult: IAsyncResult);
- var
- lBytesRead: Integer;
- lClient: Socket;
- lState: TStateObject;
- begin
- lClient := nil;
- try
- if FStopEvent.WaitOne(10, true) then
- exit;
-
- lState := TStateObject(pAsyncResult.AsyncState);
- lClient := lState.WorkSocket;
- if Assigned(lClient) and lClient.Connected then
- begin
- //We are still connected
- lBytesRead := lClient.EndReceive(pAsyncResult);
- if (lBytesRead > 0) then
- begin
- if Assigned(FOnDataReceived) then
- //Notify that data has been received
- FOnDataReceived(lClient, Encoding.ASCII.GetString(lState.Buffer, 0, lBytesRead));
- //Keep listening for more data
- lClient.BeginReceive(lState.Buffer, 0, lState.BufferSize, SocketFlags.None, ReceiveCallback, lState);
- end
- else
- begin
- FClientSocket.Shutdown(SocketShutdown.Both);
- FClientSocket.Close;
- FOnClientDisconnected(Self, FClientSocket);
- end;
- end;
- except
- on e: Exception do
- if Assigned(FOnReceiveError) then
- FOnReceiveError(lClient, e);
- end;
- end;
-
- /// <summary>
- /// Callback method called by the asynchronous BeginAccept method.
- /// </summary>
- procedure TSocketManager.AcceptCallback(pAsyncResult: IAsyncResult);
- var
- lListener: Socket;
- begin
- FConnectEvent.&Set;
-
- //Get the socket that handles the client request.
- lListener := Socket(pAsyncResult.AsyncState);
- FClientSocket := lListener.EndAccept(pAsyncResult);
- if Assigned(FOnClientConnected) then
- FOnClientConnected(FServerSocket, FClientSocket);
-
- //Pool the send and seceive thread entry points
- ThreadPool.QueueUserWorkItem(ReceiveThreadEntryPoint);
- ThreadPool.QueueUserWorkItem(SendThreadEntryPoint);
- FServerSocket.BeginAccept(AcceptCallback, FServerSocket);
- end;
-
- //***** TCP/IP Thread Entry Point Methods *****
-
- /// <summary>
- /// Send Thread entry point.
- /// </summary>
- procedure TSocketManager.SendThreadEntryPoint(pState: TObject);
- var
- lWorkQueue: Queue;
- i: integer;
- lStateObject: TStateObject;
- lBuffer: TBytes;
- lAsyncResult: IAsyncResult;
- begin
- try
- lWorkQueue := Queue.Create;
-
- while True do
- begin
- if FStopEvent.WaitOne(10, true) then
- break
- else if Assigned(FClientSocket) and FClientSocket.Connected then
- begin
- //We are still connected, so process the send queue
- FTransmitLock.AcquireWriterLock(-1);
- try
- try
- for i := 0 to FTransmitQueue.Count -1 do
- lWorkQueue.Enqueue(FTransmitQueue.DeQueue);
- except
- on e: Exception do
- if Assigned(FOnSendError) then
- FOnSendError(FClientSocket, e);
- end;
- finally
- FTransmitLock.ReleaseWriterLock;
- end;
-
- //Loop through the work queue and send all messages
- for i := 0 to lWorkQueue.Count -1 do
- begin
- //Create the State object and buffer the string
- lStateObject := TStateObject.Create(FClientSocket);
- lBuffer := Encoding.ASCII.GetBytes(lWorkQueue.DeQueue.ToString);
-
- //Send the contents of the buffer
- lAsyncResult := FClientSocket.BeginSend(lBuffer, 0, Length(lBuffer),
- SocketFlags.None, SendCallback, FClientSocket);
- end;
- end;
- end;
- except on e: Exception do
- if Assigned(FOnSendError) then
- FOnSendError(FClientSocket, e);
- end;
- end;
-
- /// <summary>
- /// Receive Thread entry point.
- /// </summary>
- procedure TSocketManager.ReceiveThreadEntryPoint(pState: TObject);
- var
- lAsyncResult: IAsyncResult;
- lStateObject: TStateObject;
- begin
- try
- while True do
- begin
- if Assigned(FClientSocket) then
- if FClientSocket.Connected then
- try
- //Start the receive operation
- lStateObject := TStateObject.Create(FClientSocket);
- lAsyncResult := FClientSocket.BeginReceive(lStateObject.Buffer,
- 0, lStateObject.BufferSize, SocketFlags.None, ReceiveCallback, lStateObject);
- if FStopEvent.WaitOne(10, true) then
- //Stop event was signalled, so break out of the loop
- break;
- except on
- e: Exception do
- if Assigned(FOnReceiveError) then
- FOnReceiveError(FClientSocket, e);
- end
- else
- begin
- if Assigned(FOnClientDisconnected) then
- FOnClientDisconnected(FServerSocket, FClientSocket);
- end;
- end;
- except
- on e: Exception do
- if Assigned(FOnReceiveError) then
- FOnReceiveError(FClientSocket, e);
- end;
- end;
-
- /// <summary>
- /// Listen thread entry point.
- /// </summary>
- procedure TSocketManager.ListenThreadEntryPoint(pState: TObject);
- begin
- try
- while True do
- try
- //Set the event to nonsignaled state.
- FConnectEvent.Reset;
- //Listen, allowing a queue of 1 connection
- FServerSocket.Listen(1);
- FServerSocket.BeginAccept(AcceptCallback, FServerSocket);
-
- if FStopEvent.WaitOne(10, true) then
- //stop event was signalled, so break out of the loop
- break;
- except on e: Exception do
- if Assigned(FOnListenError) then
- FOnListenError(FServerSocket, e);
- end;
- FServerSocket.Close;
- except on e: Exception do
- if Assigned(FOnListenError) then
- FOnListenError(FServerSocket, e);
- end;
- end;
-
- end.
-